#include <linux/init.h>         //包含宏定义得头文件
#include <linux/module.h>       //包含初始化加载模块得头文件
#include <linux/kdev_t.h>       //处理设备号宏定义的头文件
#include <linux/fs.h>           //文件操作集
#include <linux/uaccess.h>      //copy_to_user函数的文件
#include <linux/io.h>           //ioermap等函数的头文件
// #include <linux/cdevdevice.h>   //杂项设备头文件
/*包含头文件*/
/*************************************************
*注册一个字符设备驱动，上一个例程的目的是分配字符设备的设备号没有完成字符设备的注册
*（1）定义一个 cdev 的结构体
*（2）使用cdev_init函数初始化cdev结构体的成员变量(其中cdev->ops=fops就是把文件操作集写给cdev)
*（3）使用cdev_add 函数注册到内核
*（4）注销cdev结构体
*（5）使用mdev创建设备节点（mdev是udev的简化版本，udev是在pc端Linux的一种工具能根据硬件条件更
*新设备文件，包括设备文件的创建和删除，一般存在/dev目录下）
*（6）注意，字符设备注册完后不会自动生成设备节点，杂项设备是自动生成字符设备不在驱动中配置需要
* 手动使用命令创建：mknod /dev/test c 主设备号 次设备号 也可使用函数配置自动生成设备节点
*************************************************/
/*宏定义*/
#define DEVICE_NUMBER       1   //申请设备号的数量
#define DEVICE_SNAME "schrdev"  //设备名称使用cat /proc/可以查看到
#define DEVICE_ANAME "achrdev"  //设备的名称
#define DEVICE_MINOR_NUMBER 0   //次设备号的起始地址
// #define GPIO5_DR 0x02           //蜂鸣器链接的GPIO地址

/*全局变量*/
static int major_num;//存储主设备号
static int minor_num;//存储次设备号

// unsigned int*vir_gpio5_dr;  //指针的目的是为了存储分配的虚拟地址
// static int b[5];
// static int count;

/*内核传参函数*/
module_param(major_num,int,S_IRUSR);//内核传参目的是为了传递应用层手动分配的主设备号
module_param(minor_num,int,S_IRUSR);//传递次设备号
// module_param_array(b，int,&count,S_IRUSR);

/*文件操作集的结构体*/
int cdev_open(struct inode*inode,struct file * file)
{
    printk("hello cdev opnen\n");
    return 0;
}

int cdev_release(struct inode*inode,struct file * file)
{
    printk("hello cdev cdev_release byb byb!\n");
    return 0;
}

int cdev_read(struct file * file,char _user * ubuf,size_t size,loff_t*loff_t)
{
    char kbuf[64]="hello";
    if(copy_to_user(ubuf,kbuf,strlen(kbuf)!=0))//拷贝信息到应用层
    {
        printk("copy_to_user error!\n");
        return -1;
    }

    printk("hello cdev_read file!\n");
    return 0;
}

//获取到应用层的数据到内核层在改函数进行io的控制操作
int cdev_write(struct file * file,char _user * ubuf,size_t size,loff_t*loff_t)
{
     char kbuf[64]={0};
    if(copy_form_user(kbuf,ubuf,size)!=0)//从应用层拷贝数据到内核层
    {
         printk("copy_form_user error!\n");
        return -1;
    }
    printk("copy_form_user %s!\n",kbuf);
    if(kbuf[0]==1)
        vir_gpio5_dr=   
    return 0;
}

struct file_operations cdevdev_fops={
    .owner = THIS_MODULE,
    .open  = cdev_open,
    .release = cdev_release,
    .read = cdev_read,
    .write = cdev_write
};

// static cdev cdev_dev ={
//     cdev_dev.owner=THIS_MODULE, //原型struct module *owner指的是内核模块的对象指针名称
//     cdev_dev.ops  =&cdevdev_fops
// }
/*功能实现*/
static int cdevdev_init(void)
{
    dev_t dev_num;
    int reg;

    if(major_num)//major的值是由应用层传递的
    {
        dev_num = MKDEV(major_num,minor_num);
        reg=register_chrdev_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME);//只是分配设备号并没有注册字符设备下一章节会展示
        if(reg<0)
        {
            printk("chrdev register error!\n");
        }
        printk("chrdev register ok!\n");
    }
    else//动态分配主次设备号
    {
        reg=alloc_chrdev_region(&dev_num,DEVICE_MINOR_NUMBER,DEVICE_NUMBER,DEVICE_ANAME);//动态分配设备号函数
        if(reg<0)
        {
            printk("chrdev register error!\n");
        }
        printk("chrdev register ok!\n");

        //取出主次设备号并打印出来
        major_num=MAJOR(dev_num);
        minor_num=MINOR(dev_num);
        printk("chrdev major_num is:%n\n",major_num);
        printk("chrdev minor_num is:%n\n",minor_num);
    }

    cdev_dev.owner=THIS_MODULE, //原型struct module *owner指的是内核模块的对象指针名称
    cdev_init(&cdev_dev,&cdevdev_fops);//只是注册字符设备驱动，并没有创建设备节点
    cdev_add(&cdev_dev,dev_num,DEVICE_NUMBER);


    // retvalue = register_chrdev(200, "chrtest", &test_fops);

    return 0;
    //映射地址在init函数内这样加载上驱动就映射完成了
    // vir_gpio5_dr=ioermap();
}

static void cdevdev_exit(void)//退出函数没有返回值
{
    unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);//注销申请的字符设备号
    cdev_del(&cdev);
    printk("byb byb !"); 
}     


module_init(cdevdev_init);
module_exit(cdevdev_exit); 
/*驱动的出口与入口函数都在此处*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");//作者信息
/*申明信息*/
	
/*注意在开发板中加载驱动的时候一定要保持板卡的内核和编译的内核是一致的否则会报错
*查看设备节点：ls /dev/hello_cdev
*/

